#include "network.h"
#include "log.h"
#include "tool.h"
#include "database.h"

// 把字符串IP转为数字IP
uint32_t stringIPtoNum(char *ip);

// 把数字IP转为字符串IP
void NumIPtoString(uint32_t ip, char *pIP, int size);

int socket_fd()
{
    // 保存客户端的地址信息 和 套接字信息的结构体
    FD_ADDR_COUNT fd_addr_count = {.count = 0};

    // 创建一个套接字
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sock_fd)
    {
        ERROR("服务器创建套接字失败");
        exit(-1);
    }
    // 端口复用
    int val = 1; // 1代表打开地址重复利用功能，0代表关闭
    // SOL_SOCKET是通用socket设置的意思
    // SO_REUSEADDR是地址重复利用的意思
    setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));

    // 填充结构体 IP 端口 协议
    struct sockaddr_in addr;
    addr.sin_addr.s_addr = stringIPtoNum(IP);
    addr.sin_port = htons(POST);
    addr.sin_family = AF_INET;

    // 绑定
    if (-1 == bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)))
    {
        ERROR("服务器 绑定失败");
        int err = errno; // 保存当前的错误码
        fprintf(stderr, "服务器 绑定失败: %s\n", strerror(err)); // 打印详细的错误描述
        exit(-1);
    }

    // 监听 并设置允许5个客户端排队等待
    if (-1 == listen(sock_fd, 5))
    {
        ERROR("服务器监听失败");
        exit(-1);
    }

    // 创建 epoll 对象
    int epoll_fd = epoll_create1(0);
    if (-1 == epoll_fd)
    {
        ERROR("创建epoll对象失败");
        exit(-1);
    }

    // 定义 epoll 结构体 并填充 套接字 地址 事件响应机制
    struct epoll_event ev; // 定义了一个事件结构体变量
    ev.data.fd = sock_fd;  // 把监听套接字设置到事件结构体中
    ev.events = EPOLLIN;   // 把套接字感兴趣的事件设置为EPOLLIN（输入/可读事件）

    // 控制epoll 对象 添加结构体到对象中
    if (-1 == epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock_fd, &ev))
    {
        ERROR("添加结构体到epoll对象中失败");
        exit(-1);
    }

    // 创建 epoll 结构体数组 为 wait 函数第二个参数准备
    struct epoll_event evArr[MAX_FDS];

    // 多路IO复用 epoll
    while (1)
    {
        DEBUG("epoll正在等待响应");
        int ret = epoll_wait(epoll_fd, evArr, MAX_FDS, -1);
        if (ret < 0)
        {
            ERROR("epoll 等待响应失败");
            exit(-1);
        }
        else if (ret > 0)
        { // 有响应
            for (int i = 0; i < ret; i++)
            { // 循环检索
                if (evArr[i].data.fd == sock_fd)
                {
                    // 是监听套接字响应 就添加新套接字到 epoll 对象中
                    // 并保存新套接字和对方的地址
                    struct sockaddr_in clientAddr; // 存放客户端地址的结构体
                    socklen_t len = sizeof(clientAddr);
                    // 阻塞等待客户顿连接
                    DEBUG("等待客户端连接");
                    int newsock_fd = accept(sock_fd, (struct sockaddr *)&clientAddr, &len);
                    if (-1 == newsock_fd)
                    {
                        ERROR("等待客户端连接失败");
                        exit(-1);
                    }
                    // 将新的客户端的地址和套接字 保存起来
                    fd_addr_count.addrArr[fd_addr_count.count] = clientAddr;
                    fd_addr_count.sockArr[fd_addr_count.count] = newsock_fd;
                    fd_addr_count.count++;

                    // 将新的套接字 放到 epoll 对象中监视
                    struct epoll_event ev;
                    ev.data.fd = newsock_fd;
                    ev.events = EPOLLIN;
                    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, newsock_fd, &ev))
                    {
                        ERROR("添加新的事件结构体到epoll对象中失败");
                        exit(-1);
                    }

                    // 打印新的客户端信息
                    char szIP[16]; // 把手的数字IP转诶字符串IP，用来打印
                    NumIPtoString(clientAddr.sin_addr.s_addr, szIP, sizeof(szIP));
                    DEBUG("又添加了一个套接字和结构体到epoll对象中%d %s %d\n", newsock_fd, szIP,
                          ntohs(clientAddr.sin_port));
                }
                else
                {   // 是通讯套接字响应
                    int sockfd = evArr[i].data.fd; // 当前响应的通讯套接字
                    MSG m1;
                    int ret = read(evArr[i].data.fd, &m1, sizeof(m1)); // 既然套接字有可读事件，那么就读取套接字上的内容
                    if (ret <= 0)                                      // 如果接收数据出错了，就把套接字关闭，并且从集合中删除掉
                    {
                        //在表中 将套接字 绑定的 用户 在线状态 设置 为 离线 并且 更新 用户名 的 套接字 为空 
                        //在表中 将套接字 绑定的 用户 在线状态 设置 为 离线 并且 更新 用户名 的 套接字 为空 
                        //在表中 将套接字 绑定的 用户 在线状态 设置 为 离线 并且 更新 用户名 的 套接字 为空 
                        //在表中 将套接字 绑定的 用户 在线状态 设置 为 离线 并且 更新 用户名 的 套接字 为空 
                        //在表中 将套接字 绑定的 用户 在线状态 设置 为 离线 并且 更新 用户名 的 套接字 为空 
                    
                        //在关闭套接字之前 先 修改 数据库 套接字状态
                        return_fd_state(sockfd);

                        // 事件结构体里面主要填写两个变量，一个是感兴趣额的事件，另一个是套接字
                        struct epoll_event ev;         // 定义了一个事件结构体变量
                        ev.data.fd = evArr[i].data.fd; // 把监听套接字设置到事件结构体中
                        ev.events = EPOLLIN;           // 把套接字感兴趣的事件设置为EPOLLIN（输入/可读事件）
                        // 控制epoll对象，从里面删除出错的套接字和结构体
                        if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, evArr[i].data.fd, &ev) < 0)
                        {
                            perror("epoll_ctl");
                        }
                        //在epoll对象删除文件描述符之后再关闭文件描述符
                        // 不然epoll对象在删除文件描述符的时候会报错 epoll_ctl: Bad file descriptor
                        close(evArr[i].data.fd);
                        //更新客户端的地址的套接字
                        for (int d = 0; d < fd_addr_count.count; d++)
                        {
                            if (fd_addr_count.sockArr[d] == sockfd)
                            {
                                // 移动剩余的元素覆盖被删除的元素
                                memmove(&fd_addr_count.sockArr[d], &fd_addr_count.sockArr[d + 1],
                                        (fd_addr_count.count - d - 1) * sizeof(int));
                                memmove(&fd_addr_count.addrArr[d], &fd_addr_count.addrArr[d + 1],
                                        (fd_addr_count.count - d - 1) * sizeof(struct sockaddr_in));
                                fd_addr_count.count--;
                                break;
                            }
                        }

                        //更新在线的 用户和 商家 
                        for (int n = 0; n < fd_addr_count.count; n++)
                        {
                            if (fdArr[n] == sockfd)
                            {
                                // 移动剩余的元素覆盖被删除的元素
                                memmove(&fdArr[n], &fdArr[n + 1],
                                        (login_count - n - 1) * sizeof(int));
                                memmove(&nameArr[n], &nameArr[n + 1],
                                        (login_count - n - 1) * sizeof(nameArr[n] ));
                                login_count--;
                                break;
                            }
                        }

                        DEBUG("%d号套接字对应的客户端退出了\n", ev.data.fd);
                        }
                    else
                    {
                        DEBUG("network服务器接收到客户端的消息%s\n",m1.data);
                        work( sockfd , m1 );
                    }
                }
            }
        }
    }
}

// 把字符串IP转为数字IP
uint32_t stringIPtoNum(char *ip)
{
    uint32_t newIP = 0;
    // AF_INET是代表IPV4的地址的意思
    inet_pton(AF_INET, ip, &newIP);
    return newIP;
}

// 把数字IP转为字符串IP
void NumIPtoString(uint32_t ip, char *pIP, int size)
{
    inet_ntop(AF_INET, &ip, pIP, size);
}